src/pages/og-image/[slug].png.ts 2.5 K raw
1
import type { APIContext, GetStaticPathsResult } from "astro";
2
import { getCollection, getEntryBySlug } from "astro:content";
3
import satori, { SatoriOptions } from "satori";
4
import { html } from "satori-html";
5
import { Resvg } from "@resvg/resvg-js";
6
import siteConfig from "@/site-config";
7
import { getFormattedDate } from "@/utils";
8
import fs from "fs";
9
10
const monoFontReg = await fs.readFileSync("../../../CommitMono-400-Regular.otf")
11
12
const monoFontBold = await fs.readFileSync("../../../CommitMono-700-Regular.otf")
13
14
const ogOptions: SatoriOptions = {
15
  width: 1200,
16
  height: 630,
17
  // debug: true,
18
  embedFont: true,
19
  fonts: [
20
    {
21
      name: "Roboto Mono",
22
      data: await monoFontReg,
23
      weight: 400,
24
      style: "normal",
25
    },
26
    {
27
      name: "Roboto Mono",
28
      data: await monoFontBold,
29
      weight: 700,
30
      style: "normal",
31
    },
32
  ],
33
};
34
35
const markup = (title: string, pubDate: string, description: string) => html`<div
36
	tw="flex flex-col w-full h-full bg-[#191724] text-[#e0def4]"
37
>
38
	<div tw="flex flex-col flex-1 w-full p-10 justify-center">
39
		<p tw="text-2xl mb-6">${pubDate}</p>
40
		<h1 tw="text-6xl font-bold leading-snug text-white">${title}</h1>
41
		<h2 tw="text-2xl font-bold leading-snug text-white">${description}</h2>
42
	</div>
43
	<div tw="flex items-center justify-between w-full p-10 border-t border-[#7c6f64] text-xl">
44
		<div tw="flex items-center">
45
46
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" height="60">
47
        <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 002.25 15z" />
48
      </svg>
49
			<p tw="ml-3 font-semibold text-3xl">${siteConfig.title}</p>
50
		</div>
51
	</div>
52
</div>`;
53
54
export async function get({ params: { slug } }: APIContext) {
55
  const post = await getEntryBySlug("post", slug!);
56
  const title = post?.data.title ?? siteConfig.title;
57
  const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), {
58
    weekday: "long",
59
  });
60
  const description = post?.data.description ?? siteConfig.title;
61
  const svg = await satori(markup(title, postDate, description), ogOptions);
62
  const png = new Resvg(svg).render().asPng();
63
  return {
64
    body: png,
65
    encoding: "binary",
66
  };
67
}
68
69
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
70
  const posts = await getCollection("post");
71
  return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } }));
72
}